package simulation;

import java.text.DecimalFormat;

import storage.beans.Region;

import common.Constants;
import common.IGrid;

/**
 * @author cwhalen
 * 
 */
public class Earth implements IGrid {
  public int radius;
  private int gridSpacing;
  public Node grid[][];
  private int gridWidth;
  private int gridHeight;
  private long currentTime;
  private long simTimeStep; //in milliseconds
  private float sunDegStep; //derived from simTimeStep to update the display of the sun in the earth panel
  private Sun sun;  
  
  public Earth(int gridSpacing, int radius, Sun sun) {
    super();
    this.gridSpacing = gridSpacing;
    this.radius = radius;
    this.sun = sun;
    initGrid();
    initGridTemperatures();
  }
  
  private void initGridTemperatures() {
    //this is done outside the initGrid method to support the gui controls
    for (int y = 0; y < gridHeight; y++) {
      for (int x = 0; x < gridWidth; x++) {
        grid[y][x].init();
      }
    }
  }

  private void initGrid() {
    /*
     * Create the grid that holds the node objects. This grid maps to the
     * quadrants on the surface of the sphere
     */
	
    int latGridSpacing = (gridSpacing > 90)? 90 : gridSpacing;  
	  
    this.gridWidth = (((180.0 / gridSpacing) % 1) == 0) ? (180 / gridSpacing)*2      : (int)(Math.ceil((180.0 / gridSpacing))* 2);
    this.gridHeight = (((90.0 / latGridSpacing) % 1) == 0) ? (90 / latGridSpacing)*2 : (int)(Math.ceil((90.0  / latGridSpacing))*2);

//    System.out.println("Grid Dimensions: " + gridWidth + "," + gridHeight);
    
    this.grid = new Node[gridHeight][gridWidth];
    
    for (int y = 0; y < gridHeight; y++) {
      for (int x = 0; x < gridWidth; x++) {
        // Create the node
        this.grid[y][x] = new Node(gridSpacing);
        grid[y][x].init();

        if (y == 0) {
          this.grid[y][x].degLatTop = 90;
          this.grid[y][x].degLatBottom = (((90.0 / latGridSpacing) % 1) == 0) ? (this.grid[y][x].degLatTop - latGridSpacing) : 
                                          (this.grid[y][x].degLatTop - (float) (latGridSpacing * ((90.0 / latGridSpacing) % 1)));
//          System.out.println(grid[y][x].degLatBottom);
          this.grid[y][x].top = null;
        }
        else if (y > 0 && y < gridHeight - 1) {
          this.grid[y][x].degLatTop = this.grid[y - 1][x].degLatBottom;
          this.grid[y][x].degLatBottom = this.grid[y][x].degLatTop - latGridSpacing;

          this.grid[y][x].top = this.grid[y - 1][x];
          this.grid[y - 1][x].bottom = this.grid[y][x];
        }
        else if (y == (gridHeight - 1)) {
          this.grid[y][x].degLatTop = this.grid[y - 1][x].degLatBottom;
          this.grid[y][x].degLatBottom = -90;

          this.grid[y][x].top = this.grid[y - 1][x];
          this.grid[y - 1][x].bottom = this.grid[y][x];
          this.grid[y][x].bottom = null;
        }

        if (x == 0) {
          this.grid[y][x].degLonLeft = 180;
          this.grid[y][x].degLonRight = (((180.0 / gridSpacing) % 1) == 0) ? (this.grid[y][x].degLonLeft - gridSpacing) : (this.grid[y][x].degLonLeft - (float) (gridSpacing * ((180.0 / gridSpacing) % 1)));
          // this.grid[y][x].left = this.grid[y][gridWidth-1];
        }
        else if (x > 0 && x < gridWidth - 1) {
          this.grid[y][x].degLonLeft = this.grid[y][x - 1].degLonRight;
          this.grid[y][x].degLonRight = this.grid[y][x].degLonLeft - gridSpacing;

          this.grid[y][x].left = this.grid[y][x - 1];
          this.grid[y][x - 1].right = this.grid[y][x];
        }
        else if (x == (gridWidth - 1)) {
          this.grid[y][x].degLonLeft = this.grid[y][x - 1].degLonRight;
          this.grid[y][x].degLonRight = -180;

          this.grid[y][x].left = this.grid[y][x - 1];
          this.grid[y][x - 1].right = this.grid[y][x];
          this.grid[y][x].right = this.grid[y][0];
          this.grid[y][0].left = this.grid[y][x];
        }

        this.grid[y][x].cGeometry(this.radius);
      }
    }
  }
  
  /**
   * @param sun
   */
  public void step() {
    long minsPerStep = simTimeStep/1000/60;
    for(int i = 0; i < minsPerStep; i++) {
      float globalHeatIncrease = 0;
      for (int y = 0; y < gridHeight; y++) {
        for (int x = 0; x < gridWidth; x++) {
          this.grid[y][x].cTemp(sun);
          globalHeatIncrease += grid[y][x].newTemp - grid[y][x].oldTemp;          
        }
      }
      sun.moveSun(Constants.DEG_PER_MIN);
      this.cCooling(globalHeatIncrease);
      this.swap();
    }
    currentTime += simTimeStep;
//System.out.println("[simulation.Earth]eModel time: "+currentTime+ "    time step: "+simTimeStep);
  }
  
  public void setCurrentTime(long time) {
//System.out.println("setting eModel time to: "+currentTime);  
    currentTime = time;
  }
  
  /**
   * 
   */
  public void cCooling(float globalHeatIncrease) {
    for (int y = 0; y < gridHeight; y += 1) {
      for (int x = 0; x < gridWidth; x += 1) {
        this.grid[y][x].cCool(globalHeatIncrease, (gridWidth * gridHeight) / 2);
      }
    }
  }

  /**
   * 
   */
  public void swap() {
    for (int y = 0; y < gridHeight; y += 1) {
      for (int x = 0; x < gridWidth; x += 1) {
        this.grid[y][x].swapTemp();
      }
    }
  }

  public String toString() {
    String result = "";
    for (int y = 0; y < gridHeight; y += 1) {
      for (int x = 0; x < gridWidth; x += 1) {
        result += "|" + new DecimalFormat("0.00").format(this.grid[y][x].newTemp) + "|";
      }
      result += "\n";
    }
    return result;
  }

  public void print() {
    for (int y = 0; y < gridHeight; y += 1) {
      for (int x = 0; x < gridWidth; x += 1) {
        System.out.print("|" + new DecimalFormat("0.00").format(this.grid[y][x].newTemp) + "|");

      }
      System.out.print("\n");
    }
  }

  /**
   * @return
   */
  public double totalTemp() {
    double result = 0;

    for (int y = 0; y < gridHeight; y += 1) {
      for (int x = 0; x < gridWidth; x += 1) {
        result += this.grid[y][x].oldTemp;
      }
    }
    return result;
  }

  public double getTemperature(int x, int y) {
    return this.grid[y][x].newTemp;
  }
  
  public void setGridSpacing(int gridSpacing, int newRadius) {
    this.gridSpacing = gridSpacing;
    this.radius = newRadius;
    initGrid();
  }
  
  public void reset() {
    initGrid();
    initGridTemperatures();
    sun.reset();
  }
  
  public void setSimTimeStep(int minutes) {
    simTimeStep = minutes * 60 * 1000;
    sunDegStep = Constants.DEG_PER_MIN * (float)minutes;
  }
  
  public long getSimTimeStep() {
    return simTimeStep;
  }
  
  public float getSunDegStep() {
    return sunDegStep;
  }
  
  public float getCellHeight(int x, int y) {
    return grid[y][x].height;    
  }
  
  public int getWidth() {
    return gridWidth;
  }
  
  public int getHeight() {
    return gridHeight;
  }
  
  public Region getRegion(int x, int y) {
    Node cell = grid[y][x];
    return new Region((int)cell.degLonLeft, 
                      (int)cell.degLonRight, 
                      (int)cell.degLatTop, 
                      (int)cell.degLatBottom);
  }
  
  public IGrid copy() {
    return new EarthClone(grid, gridWidth, gridHeight, currentTime, gridSpacing);
  }
  
  public long getTime() {
    return currentTime;
  }
  
  public int getGridSpacing() {
    return gridSpacing;
  }
}
